package com.basker.pisces.script;

import java.util.Map;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class ScriptManager {

    public static final String GROOVY = "groovy";

    private static final ScriptEngineManager MANAGER = new ScriptEngineManager();

    public static CompiledScript compile(@Nullable String scriptType, String script) throws ScriptException {
        Assert.hasText(script, "parameter 'script' is required");
        scriptType = StringUtils.isEmpty(scriptType) ? GROOVY : scriptType;

        ScriptEngine engine = getEngine(scriptType);
        return ((Compilable) engine).compile(script);
    }

    public static Object eval(CompiledScript script, Object bindings) throws ScriptException {
        Assert.notNull(script, "parameter 'script' is required");

        Bindings resolvedBindings = resolveBindings(bindings);
        return script.eval(resolvedBindings);
    }

    public static Object eval(@Nullable String scriptType, String script, Object bindings) throws ScriptException {
        Assert.hasText(script, "parameter 'script' is required");
        scriptType = StringUtils.isEmpty(scriptType) ? GROOVY : scriptType;

        ScriptEngine engine = getEngine(scriptType);
        Bindings resolvedBindings = resolveBindings(bindings);

        return engine.eval(script, resolvedBindings);
    }

    private static ScriptEngine getEngine(String scriptType) {
        ScriptEngine engine = MANAGER.getEngineByName(scriptType);

        if (engine == null) {
            throw new RuntimeException("scriptType '" + scriptType + "' is not found");
        }

        return engine;
    }

    @SuppressWarnings("unchecked")
    private static Bindings resolveBindings(Object bindings) {
        if (bindings == null) {
            return new SimpleBindings();
        }

        if (bindings instanceof Bindings) {
            return (Bindings) bindings;
        }

        if (bindings instanceof Map<?, ?>) {
            return new SimpleBindings((Map<String, Object>) bindings);
        }

        return new SimpleBindings(JSONObject.parseObject(JSON.toJSONString(bindings)));
    }
}
